Add AgentOS: Full-stack AI agent management dashboard#1469
Add AgentOS: Full-stack AI agent management dashboard#1469cachopin wants to merge 10 commits intoMervinPraison:mainfrom
Conversation
…up script Initialize the PraisonAI platform by adding a main entry point, defining project dependencies in pyproject.toml, creating a README for project documentation, and configuring the run script for the FastAPI server. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 0900f50b-e663-41d6-8eb3-0fb68d06e78d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: ca2d51cc-b5aa-4b1d-9829-37bc47bbd705 Replit-Helium-Checkpoint-Created: true
Implement a full-stack AgentOS dashboard with React/Vite frontend and FastAPI backend, enabling agent CRUD operations, chat, and activity logging. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 0900f50b-e663-41d6-8eb3-0fb68d06e78d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: d28e2992-56b7-45f3-9e96-b8ca2a548f7d Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6e90e19c-4e5e-4ff7-9a97-710a94bbd6a8/0900f50b-e663-41d6-8eb3-0fb68d06e78d/8nsyWBB Replit-Helium-Checkpoint-Created: true
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 0900f50b-e663-41d6-8eb3-0fb68d06e78d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 0d762fb5-ec75-4083-b52b-16f8976607ce Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6e90e19c-4e5e-4ff7-9a97-710a94bbd6a8/0900f50b-e663-41d6-8eb3-0fb68d06e78d/8nsyWBB Replit-Helium-Checkpoint-Created: true
Introduces backend/db.py with an `agents` table matching the new schema (id, name, instructions, model, status, generation, parent_id, performance_score, token_spend, exit_summary, created_at, updated_at) and a one-time migration from agents_store.json on first startup when the table is empty. Status is constrained to active/auditing/ decommissioned and parent_id is a self-referencing FK. FastAPI endpoints are preserved. `llm` is kept as a two-way alias for `model` for backwards compatibility, and legacy body fields (role, tools, connections) are accepted but ignored so existing clients do not break.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a full-stack AgentOS project: Replit workspace config, React/Vite frontend, FastAPI backend with SQLite persistence and JSON→SQL migration, REST endpoints for agent CRUD/chat/history/activity, frontend UI components, and scripts/docs to run the app. Changes
Sequence Diagram(s)sequenceDiagram
participant User as Frontend (User)
participant API as Backend API
participant DB as SQLite DB
participant Agent as PraisonAI Agent
User->>API: POST /agents/{id}/chat (message)
activate API
API->>DB: SELECT agent by id
DB-->>API: agent config
alt OPENAI_API_KEY present
API->>Agent: run agent with message
activate Agent
Agent-->>API: response + activity
deactivate Agent
else demo fallback
API-->>API: generate demo response
end
API->>DB: INSERT history entry (with activity JSON)
DB-->>API: inserted entry
API-->>User: chat response + persisted entry
deactivate API
sequenceDiagram
participant User as Frontend (User)
participant API as Backend API
participant DB as SQLite DB
User->>API: POST /agents (create agent)
activate API
API->>DB: INSERT agent row
DB-->>API: created agent
API-->>User: created agent
deactivate API
User->>API: GET /agents
activate API
API->>DB: SELECT * FROM agents ORDER BY created_at
DB-->>API: agents list
API-->>User: agents list
deactivate API
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first — incorporate their findings. Review areas:
|
There was a problem hiding this comment.
Code Review
This pull request implements "AgentOS," a full-stack web dashboard for managing AI agents using a React frontend and a FastAPI backend. The system utilizes SQLite for agent persistence and JSON files for conversation history. Valid feedback identifies several critical architectural and logic issues: the JSON-based history storage is not thread-safe and should be moved to the database; the frontend status options in the agent form mismatch the backend's allowed values, which will cause validation errors; and the agent deletion logic lacks error handling, potentially leading to a desync between the UI and the database. Additionally, the code should be updated to replace deprecated features, such as the FastAPI startup event handler and non-timezone-aware datetime methods, with modern standards.
| def load_history() -> Dict: | ||
| if not os.path.exists(HISTORY_FILE): | ||
| return {} | ||
| with open(HISTORY_FILE) as f: | ||
| return json.load(f) | ||
|
|
||
|
|
||
| def save_history(history: Dict) -> None: | ||
| with open(HISTORY_FILE, "w") as f: | ||
| json.dump(history, f, indent=2) |
There was a problem hiding this comment.
The current implementation of conversation history using a single JSON file is not thread-safe. FastAPI executes synchronous def endpoints in a thread pool, meaning concurrent requests (e.g., multiple users chatting or deleting history simultaneously) can lead to race conditions where one request's data overwrites another's during the load_history/save_history cycle. Since you are already using SQLite for agent definitions, it is highly recommended to move conversation history into a database table to ensure data integrity and atomicity.
| </Field> | ||
| <Field label="Status"> | ||
| <div className="flex gap-3 pt-1"> | ||
| {['active', 'inactive'].map(s => ( |
There was a problem hiding this comment.
The status options in the form (active, inactive) do not match the allowed statuses in the backend (active, auditing, decommissioned). Selecting inactive will result in a 400 Bad Request error from the backend validation logic. The form should be updated to align with the database schema.
| {['active', 'inactive'].map(s => ( | |
| {['active', 'auditing', 'decommissioned'].map(s => ( |
| def _now() -> str: | ||
| return datetime.utcnow().isoformat() + "Z" |
There was a problem hiding this comment.
| @app.on_event("startup") | ||
| def _startup() -> None: |
| const handleDeleteAgent = async (id) => { | ||
| if (!window.confirm('Delete this agent?')) return | ||
| await fetch(`${API}/agents/${id}`, { method: 'DELETE' }) | ||
| setAgents(prev => prev.filter(a => a.id !== id)) | ||
| if (selectedId === id) { | ||
| const remaining = agents.filter(a => a.id !== id) | ||
| setSelectedId(remaining.length > 0 ? remaining[0].id : null) | ||
| } | ||
| } |
There was a problem hiding this comment.
The handleDeleteAgent function does not check if the DELETE request was successful before updating the local state. If the server request fails, the agent will disappear from the UI but remain in the database, causing a desync. You should wrap the fetch call in a try-catch block and verify res.ok before updating the agents state.
const handleDeleteAgent = async (id) => {
if (!window.confirm('Delete this agent?')) return
try {
const res = await fetch(`${API}/agents/${id}`, { method: 'DELETE' })
if (!res.ok) throw new Error('Failed to delete agent')
setAgents(prev => prev.filter(a => a.id !== id))
if (selectedId === id) {
const remaining = agents.filter(a => a.id !== id)
setSelectedId(remaining.length > 0 ? remaining[0].id : null)
}
} catch (e) {
console.error(e)
alert('Could not delete agent. Please try again.')
}
}
Greptile SummaryThis PR introduces AgentOS, a full-stack AI agent management dashboard consisting of a React/Vite frontend and a FastAPI backend with SQLite persistence. The addition is entirely new — no existing code is modified. Several issues flagged in prior review rounds have been addressed (status validation, error handling, missing dependency, Key changes:
Issues found:
Confidence Score: 3/5Not safe to merge as-is due to the committed binary database artifact; the CORS startup-crash from the prior review also remains unresolved. Multiple issues from the previous review round were fixed (status values, error handling, missing dependency, AgentDetail crash), which is meaningful progress. However, two blockers persist: (1) the CORS wildcard + allow_credentials=True combination that crashes the server at startup (flagged in a prior thread, not yet resolved), and (2) the committed platform.db binary artifact that permanently bloats git history. The two newly found items (missing agent-existence guard in clear_history, migration dropping generation) are non-blocking P2s. platform.db (remove from tracking), backend/main.py (CORS credentials conflict, clear_history agent guard) Important Files Changed
Sequence DiagramsequenceDiagram
participant Browser as React Frontend
participant Vite as Vite Dev Server (5000)
participant API as FastAPI Backend (8000)
participant DB as SQLite (agentos.db)
Browser->>Vite: GET /api/agents
Vite->>API: GET /agents (proxy)
API->>DB: SELECT * FROM agents
DB-->>API: rows
API-->>Browser: JSON agent list
Browser->>Vite: POST /api/agents/{id}/chat
Vite->>API: POST /agents/{id}/chat
API->>DB: SELECT agent by id
DB-->>API: agent row
alt OPENAI_API_KEY set
API->>API: praisonaiagents.Agent.start()
else Demo mode
API->>API: _demo_response()
end
API->>DB: INSERT INTO chat_history
API-->>Browser: user_message, agent_response, activity
Browser->>Vite: DELETE /api/agents/{id}/history
Vite->>API: DELETE /agents/{id}/history
Note over API: No agent-existence check
API->>DB: DELETE FROM chat_history WHERE agent_id=?
API-->>Browser: 204 No Content
|
- Add chat_history table with FK to agents(id) ON DELETE CASCADE and
(agent_id, timestamp) index; migrate existing history_store.json on
first startup when the table is empty.
- Replace the deprecated @app.on_event("startup") hook with a FastAPI
lifespan context manager.
- Swap datetime.utcnow() for timezone-aware datetime.now(timezone.utc).
- Drop the non-thread-safe load_history/save_history JSON helpers; chat
write/read/clear/activity endpoints now go through db.py, and agent
deletion lets the FK cascade clean up history rows.
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (4)
backend/history_store.json (1)
1-1: Runtime data file tracked in VCS.
history_store.jsonis mutable runtime state written by the chat endpoint; checking it into the repo will cause merge conflicts and accidental commits of conversation data. Prefer creating it lazily on first write and adding it to.gitignore. Same consideration applies tobackend/agents_store.json(seed data can live in code or a*.sample.json).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/history_store.json` at line 1, Remove the tracked mutable runtime files (history_store.json and agents_store.json) from version control and stop committing them: delete them from the repo index (git rm --cached) and add their names to .gitignore; change the runtime code that writes/reads these files to create them lazily on first write if they don’t exist (use a safe "create-if-missing" path in the chat/agent storage logic) and keep any seed data in code or in sample files (e.g., agents_store.sample.json) instead of committing the mutable JSON files.run.py (1)
1-11: Propagate npm exit code and simplify env.A few small issues:
subprocess.run(...)ignores the child exit code, so a failingnpm run devexitsrun.pywith status 0 and Replit/CI will think startup succeeded.env={**os.environ}is a no-op copy; just omitenv=to inherit the parent environment.sysis imported but unused.- Ruff S607 (partial executable path) is typically acceptable for
npmon dev machines, but you can silence it with a# noqa: S607comment or resolve viashutil.which("npm")if you want it clean.🔧 Proposed fix
"""Start the AgentOS frontend dev server on port 5000.""" import os import subprocess -import sys if __name__ == "__main__": os.chdir(os.path.join(os.path.dirname(__file__), "frontend")) - subprocess.run( - ["npm", "run", "dev"], - env={**os.environ}, - ) + result = subprocess.run(["npm", "run", "dev"]) # noqa: S607 + raise SystemExit(result.returncode)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@run.py` around lines 1 - 11, The script currently ignores the child process exit code and uses a no-op env copy; update run.py so after changing directory with os.chdir(...) you call subprocess.run(["npm", "run", "dev"]) (omit env={**os.environ}) and capture its CompletedProcess, then propagate the exit status with sys.exit(result.returncode) using the existing sys import; optionally silence Ruff S607 for the "npm" invocation by adding a "# noqa: S607" comment to the subprocess.run call or resolve the binary with shutil.which("npm") before invoking.attached_assets/Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txt (1)
1-168: Consider removing this spec artifact from the repo.Pasted prompt files under
attached_assets/are Replit-scratchpad artifacts, not project deliverables. It will also drift from the actual implementation (e.g., the spec mandates "JSON file storage only (no SQLite, no Postgres)" at line 154, but this PR intentionally migrates to SQLite). Consider moving this content into a properdocs/orREADME.md(updated to reflect current state) or deleting it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@attached_assets/Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txt` around lines 1 - 168, This PR includes a pasted spec artifact file (Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txt) that should not be committed as a project deliverable; either delete this file from the branch or move its content into a proper docs/README.md (update the spec to match the current implementation, e.g., note the SQLite migration and any changed constraints such as storage), and add/update .gitignore to exclude similar attached_assets scratchpad files so future paste artifacts are not committed.frontend/index.html (1)
11-11: Tailwind CDN is not meant for production.
cdn.tailwindcss.comis the Play CDN — it runs the JIT in the browser, prints a console warning in production builds, and blocks first paint until the script loads. Once the UI stabilizes, migrate to Tailwind as a dev dependency + PostCSS build so you ship a compiled stylesheet instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/index.html` at line 11, The page currently loads Tailwind via the Play CDN using the <script src="https://cdn.tailwindcss.com"> tag in frontend/index.html; remove that script and migrate Tailwind to a build step by adding it as a devDependency, create tailwind.config.js, add a source CSS (e.g., src/styles/tailwind.css) with the `@tailwind` base/components/utilities directives, configure PostCSS (or your bundler) to run tailwind and purge unused styles, add an npm script to build the compiled CSS (e.g., build:css) and include the resulting compiled stylesheet via a <link> tag in frontend/index.html instead of the CDN script so production serves a precompiled stylesheet.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/db.py`:
- Around line 15-30: The agents table schema (SCHEMA) and the DB create/update
paths are missing the user-editable fields sent by AgentForm: role, tools, and
connections, so those values are dropped and the UI shows empty agent.role;
update the SCHEMA to add columns role TEXT, tools TEXT (or JSON string), and
connections TEXT (or JSON string) to the agents table and migrate/ensure
existing DBs handle the new columns, then update the create and update code
paths (the INSERT and UPDATE logic and the whitelist used when persisting in
backend/db.py) to include these fields and ensure API responses that return
agent rows include role, tools, and connections so the frontend receives the
persisted values.
In `@backend/main.py`:
- Around line 33-42: Current load_history/save_history perform non-atomic file
writes and have no process-level locking; fix by adding a process-safe lock
around read-modify-write and doing atomic replace on write: in save_history (and
any callers that read-modify-write, e.g., chat and clear-history) acquire a lock
(e.g., open HISTORY_FILE + ".lock" and fcntl/portalocker) before reading or
writing, write JSON to a temporary file in the same directory, fsync the temp
file and its directory, then atomically replace the original with
os.replace(tmp_path, HISTORY_FILE); ensure load_history also acquires the same
lock when used in read-modify-write sequences; alternatively migrate history
storage to SQLite if you prefer a transactional DB.
- Around line 216-223: The get_activity handler builds activity in newest-first
order by iterating reversed(all_entries) but then returns activity[-100:], which
keeps the oldest tail; change the final slice to return the newest 100 items
(use activity[:100]) so the function returns the most recent 100 activity
entries; update the return statement in get_activity (which calls load_history
and iterates all_entries into activity) to slice the first 100 elements of
activity.
- Around line 21-27: Replace the permissive CORS configuration in the
app.add_middleware(CORSMiddleware, ...) block: stop using allow_origins=["*"]
together with allow_credentials=True; instead load allowed origins from
config/env (e.g., FRONTEND_ORIGINS or settings.ALLOWED_ORIGINS) and set
allow_origins to that explicit list while keeping allow_credentials as needed,
or set allow_credentials=False if credentials aren't required; update the
CORSMiddleware call where app.add_middleware and the
allow_origins/allow_credentials parameters are defined to use the configured
origins variable.
In `@frontend/src/App.jsx`:
- Around line 32-55: Requests in fetchHistory and fetchActivity can overwrite
state when responses arrive after the user switches agents; modify both
functions to either (A) use an AbortController per call and signal.abort() any
previous controller before starting a new fetch, wiring the controller.signal
into fetch and catching AbortError, or (B) capture the id passed into
fetchHistory/fetchActivity and only call setHistory/setActivity if the current
selectedId still matches that id (or if a saved requestId matches). Also ensure
you clean up/abort outstanding requests in the useEffect that triggers on
selectedId change so stale responses never update state.
- Around line 81-85: In handleClearHistory, clear the activity state in addition
to history and validate the DELETE response: after confirming selectedId, await
the fetch to `${API}/agents/${selectedId}/history`, check response.ok
(handle/log non-ok responses or throw) and only then call setHistory([]) and
setActivity([]) so ActivityLog (which reads activity) is not left with stale
entries; reference the handleClearHistory function and the
setHistory/setActivity state setters when applying the fix.
- Around line 66-73: The fetch call to `${API}/agents/${selectedId}/chat` should
check the HTTP response before parsing and updating state: after awaiting res,
verify res.ok and handle non-2xx responses (e.g., parse error body or throw a
descriptive Error) so you don't append an error payload to history; then parse
JSON and update state, but guard the activity spread in setActivity by using a
defensive fallback like Array.isArray(entry.activity) ? entry.activity : [] to
avoid TypeError when entry.activity is missing; update the logic around the
fetch, the res handling, and the setHistory/setActivity calls (referencing
variables/functions selectedId, res, entry, setHistory, setActivity, and
entry.activity).
- Around line 87-118: The handlers handleSaveAgent and handleDeleteAgent mutate
local state without checking HTTP response status; update both to check res.ok
(and wrap fetch in try/catch) before calling res.json or updating
setAgents/setSelectedId/setShowForm/setEditingAgent: on non-ok, parse and
surface the error (or show a user-facing message) and abort state changes; only
on successful responses use the parsed JSON (updated/created) to update agents
and adjust selection; for delete ensure you confirm res.ok before filtering
agents and changing selectedId.
In `@frontend/src/components/AgentDetail.jsx`:
- Around line 7-12: toolsUsed is being populated from activity.description which
contains free-text log messages (a.description) rather than a tool identifier,
causing incorrect "Tools Used" counts; update AgentDetail.jsx to key toolsUsed
by a structured tool id (e.g., activity.tool or activity.tool_id) if present, or
fall back to deriving the count from the agent definition (agent.tools) instead
of entry.activity descriptions—locate the toolsUsed Set, the history.forEach
loop, and the activity entry checks (entry.activity, a.type, a.description) and
change the logic to use the structured tool field or agent.tools to compute
distinct tools.
- Around line 95-121: agent.tools and agent.connections can be undefined causing
TypeError; update the rendering in the AgentDetail component to guard/default
these fields to empty arrays before reading length or mapping (use expressions
like (agent.tools || []).length and (agent.tools || []).map(...) and similarly
for agent.connections), and keep the same UI elements (Section, TOOL_ICONS
lookups) but iterate the safe array; also add a short comment noting server-side
may omit these fields so defensive defaults are required here.
In `@frontend/src/components/AgentForm.jsx`:
- Around line 49-53: The frontend is submitting role, tools, and connections via
AgentForm.jsx (handleSubmit) but backend models AgentCreate/AgentUpdate
(ConfigDict(extra="ignore") in backend/main.py) and the agents table
(backend/db.py) don't include those fields so they are dropped and later cause
AgentDetail.jsx to fail; fix this by either adding persistent support (add role,
tools, connections columns to the agents table schema in backend/db.py, extend
the Pydantic/ConfigDict models AgentCreate and AgentUpdate to declare these
fields with appropriate types, and update create/update handlers in
backend/main.py to accept and persist them) or remove the fields/validation from
the form (modify AgentForm.jsx handleSubmit and required checks) and ensure
AgentDetail.jsx guards against undefined tools/connections — choose the
preferred route (server-side persistence recommended) and make matching changes
across DB schema, Pydantic models, and create/update handlers so submitted
fields are stored and returned.
- Around line 126-142: The status options in the UI (the array used inside Field
rendering) must match the backend's ALLOWED_STATUS to avoid 400s: replace the
['active','inactive'] array with ['active','auditing','decommissioned'] in
AgentForm.jsx, keep using set('status', s) and form.status for selection
highlighting, and ensure the form initialization/prefill logic preserves
existing form.status values so legacy statuses (e.g., 'auditing') remain
selectable and saveable; do not change backend validation in _validate_status
here.
In `@frontend/src/components/ChatPanel.jsx`:
- Around line 128-148: Add accessible names: give the text input an explicit
aria-label (e.g., aria-label={`Message ${agent.name}`} or associate a visible
<label>) instead of relying only on placeholder, and give the submit button an
accessible name (e.g., aria-label="Send message" or include a visually-hidden
text node like <span className="sr-only">Send</span>) so the icon-only button is
announced by screen readers; update the JSX for the input and the button that
use the input, loading, and agent.name identifiers accordingly.
In `@frontend/src/components/Sidebar.jsx`:
- Around line 50-52: The UI is reading agent.role but the backend DB and API
don't provide that field (agents table / backend/db.py and _migrate_from_json()
removed it), so either restore it in the backend or stop using it in the
frontend; to fix, decide which approach: (A) add a "role" column to the agents
table, update the migration _migrate_from_json() and any ORM/serialization so
the API returns role (ensure create/update paths set it), and update Sidebar.jsx
/ ChatPanel.jsx / AgentDetail.jsx to use the returned role; or (B)
remove/replace agent.role in Sidebar.jsx, ChatPanel.jsx, and AgentDetail.jsx
with an existing field (e.g., agent.instructions or agent.name) and adjust UI
text accordingly so it doesn't rely on undefined agent.role. Ensure references
to agent.role are removed or backed by the new DB/API field so the subtitle
renders correctly.
In `@main.py`:
- Around line 1-6: Delete the placeholder module main.py (which only defines the
unused main() function and prints "Hello from repl-nix-workspace!") because the
app's real entry points are run.py and backend/main.py; remove the file and any
accidental imports referencing main.main to avoid confusion about startup
behavior.
In `@pyproject.toml`:
- Around line 1-15: Update project metadata and dependencies in pyproject.toml:
change name and description from the Replit defaults to a meaningful project
name/description (e.g., use "agentos" and a real description), remove unused
dependencies aiosqlite, sqlalchemy, httpx, passlib[bcrypt], pyjwt and drop the
pydantic[email] extra since email validation isn’t used, and add the missing
runtime dependency "praisonaiagents" (imported in backend/main.py as pra i s o n
a i a g e n t s) so the chat endpoint won't raise ModuleNotFoundError; ensure
the requires-python and remaining deps (fastapi, uvicorn, pydantic) are
preserved.
In `@replit.md`:
- Around line 24-30: Update the Backend (FastAPI) docs to reflect the SQLite
migration: change the comment for main.py to indicate it uses SQLite storage via
backend/db.py and that agent definitions are now persisted in the SQLite DB
(mention the SQLite DB file created by backend/db.py, e.g., agents.db) while
agents_store.json is retained only as a one-time legacy seed; keep
history_store.json listed if conversation history is still JSON-persisted. Also
add a language hint to both fenced code blocks (for example ```text) to satisfy
markdownlint MD040.
---
Nitpick comments:
In
`@attached_assets/Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txt`:
- Around line 1-168: This PR includes a pasted spec artifact file
(Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txt)
that should not be committed as a project deliverable; either delete this file
from the branch or move its content into a proper docs/README.md (update the
spec to match the current implementation, e.g., note the SQLite migration and
any changed constraints such as storage), and add/update .gitignore to exclude
similar attached_assets scratchpad files so future paste artifacts are not
committed.
In `@backend/history_store.json`:
- Line 1: Remove the tracked mutable runtime files (history_store.json and
agents_store.json) from version control and stop committing them: delete them
from the repo index (git rm --cached) and add their names to .gitignore; change
the runtime code that writes/reads these files to create them lazily on first
write if they don’t exist (use a safe "create-if-missing" path in the chat/agent
storage logic) and keep any seed data in code or in sample files (e.g.,
agents_store.sample.json) instead of committing the mutable JSON files.
In `@frontend/index.html`:
- Line 11: The page currently loads Tailwind via the Play CDN using the <script
src="https://cdn.tailwindcss.com"> tag in frontend/index.html; remove that
script and migrate Tailwind to a build step by adding it as a devDependency,
create tailwind.config.js, add a source CSS (e.g., src/styles/tailwind.css) with
the `@tailwind` base/components/utilities directives, configure PostCSS (or your
bundler) to run tailwind and purge unused styles, add an npm script to build the
compiled CSS (e.g., build:css) and include the resulting compiled stylesheet via
a <link> tag in frontend/index.html instead of the CDN script so production
serves a precompiled stylesheet.
In `@run.py`:
- Around line 1-11: The script currently ignores the child process exit code and
uses a no-op env copy; update run.py so after changing directory with
os.chdir(...) you call subprocess.run(["npm", "run", "dev"]) (omit
env={**os.environ}) and capture its CompletedProcess, then propagate the exit
status with sys.exit(result.returncode) using the existing sys import;
optionally silence Ruff S607 for the "npm" invocation by adding a "# noqa: S607"
comment to the subprocess.run call or resolve the binary with
shutil.which("npm") before invoking.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4eb57073-f4f0-4834-a30f-e45bdc2c1cb1
⛔ Files ignored due to path filters (3)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonplatform.dbis excluded by!**/*.dbuv.lockis excluded by!**/*.lock
📒 Files selected for processing (20)
.replitattached_assets/Pasted-Build-a-clean-modern-web-dashboard-for-managing-and-int_1776597969858.txtbackend/agents_store.jsonbackend/db.pybackend/history_store.jsonbackend/main.pyfrontend/index.htmlfrontend/package.jsonfrontend/src/App.jsxfrontend/src/components/ActivityLog.jsxfrontend/src/components/AgentDetail.jsxfrontend/src/components/AgentForm.jsxfrontend/src/components/ChatPanel.jsxfrontend/src/components/Sidebar.jsxfrontend/src/main.jsxfrontend/vite.config.jsmain.pypyproject.tomlreplit.mdrun.py
| SCHEMA = """ | ||
| CREATE TABLE IF NOT EXISTS agents ( | ||
| id TEXT PRIMARY KEY, | ||
| name TEXT NOT NULL, | ||
| instructions TEXT NOT NULL DEFAULT '', | ||
| model TEXT NOT NULL, | ||
| status TEXT NOT NULL DEFAULT 'active' | ||
| CHECK (status IN ('active', 'auditing', 'decommissioned')), | ||
| generation INTEGER NOT NULL DEFAULT 1, | ||
| parent_id TEXT REFERENCES agents(id) ON DELETE SET NULL, | ||
| performance_score REAL, | ||
| token_spend INTEGER NOT NULL DEFAULT 0, | ||
| exit_summary TEXT, | ||
| created_at TEXT NOT NULL, | ||
| updated_at TEXT NOT NULL | ||
| ); |
There was a problem hiding this comment.
Persist the fields the frontend lets users edit.
AgentForm sends role, tools, and connections, but the SQLite schema, insert path, and update whitelist drop them. After create/update or reload, those user inputs are lost and UI reads like agent.role render empty.
Either add these fields to persistence/API responses or remove them from the form before release.
Also applies to: 113-138, 141-151
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/db.py` around lines 15 - 30, The agents table schema (SCHEMA) and the
DB create/update paths are missing the user-editable fields sent by AgentForm:
role, tools, and connections, so those values are dropped and the UI shows empty
agent.role; update the SCHEMA to add columns role TEXT, tools TEXT (or JSON
string), and connections TEXT (or JSON string) to the agents table and
migrate/ensure existing DBs handle the new columns, then update the create and
update code paths (the INSERT and UPDATE logic and the whitelist used when
persisting in backend/db.py) to include these fields and ensure API responses
that return agent rows include role, tools, and connections so the frontend
receives the persisted values.
| app.add_middleware( | ||
| CORSMiddleware, | ||
| allow_origins=["*"], | ||
| allow_credentials=True, | ||
| allow_methods=["*"], | ||
| allow_headers=["*"], | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the backend/main.py file around the CORS configuration
cat -n backend/main.py | head -40Repository: MervinPraison/PraisonAI
Length of output: 1372
🏁 Script executed:
# Check if there are any environment variables or config files related to CORS
rg -i "cors|origin" --type py backend/ -A 2 -B 2Repository: MervinPraison/PraisonAI
Length of output: 503
🏁 Script executed:
# Search for frontend configuration or hardcoded origins
rg -i "localhost.*5173|5173|frontend.*origin" . -A 2 -B 2Repository: MervinPraison/PraisonAI
Length of output: 24012
🏁 Script executed:
# Check if allow_credentials is used elsewhere or if there's auth/session setup
rg -i "credentials|session|auth|cookie" backend/main.py -A 2 -B 2Repository: MervinPraison/PraisonAI
Length of output: 189
🏁 Script executed:
# Check for authentication, session, or sensitive operations in backend
rg -i "jwt|token|password|secret|bearer|authorization" backend/ -A 2Repository: MervinPraison/PraisonAI
Length of output: 1242
🏁 Script executed:
# Check what endpoints exist and their purpose
rg -i "@app\.(get|post|put|delete|patch)" backend/main.py -B 2 -A 5Repository: MervinPraison/PraisonAI
Length of output: 1898
Restrict CORS before enabling credentials.
allow_origins=["*"] with allow_credentials=True is too permissive. Use explicit frontend origins from config/env. The API currently has no authentication, but this configuration is a security anti-pattern that will become critical if authentication is added later without updating CORS.
🔒 Proposed direction
+allowed_origins = os.environ.get("AGENTOS_CORS_ORIGINS", "http://localhost:5173").split(",")
+
app.add_middleware(
CORSMiddleware,
- allow_origins=["*"],
+ allow_origins=allowed_origins,
allow_credentials=True,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| allowed_origins = os.environ.get("AGENTOS_CORS_ORIGINS", "http://localhost:5173").split(",") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=allowed_origins, | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/main.py` around lines 21 - 27, Replace the permissive CORS
configuration in the app.add_middleware(CORSMiddleware, ...) block: stop using
allow_origins=["*"] together with allow_credentials=True; instead load allowed
origins from config/env (e.g., FRONTEND_ORIGINS or settings.ALLOWED_ORIGINS) and
set allow_origins to that explicit list while keeping allow_credentials as
needed, or set allow_credentials=False if credentials aren't required; update
the CORSMiddleware call where app.add_middleware and the
allow_origins/allow_credentials parameters are defined to use the configured
origins variable.
| <input | ||
| type="text" | ||
| value={input} | ||
| onChange={e => setInput(e.target.value)} | ||
| placeholder={`Message ${agent.name}...`} | ||
| className="flex-1 px-4 py-2.5 rounded-xl text-sm outline-none transition-colors" | ||
| style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }} | ||
| onFocus={e => e.target.style.borderColor = '#6c63ff'} | ||
| onBlur={e => e.target.style.borderColor = '#2a2d3e'} | ||
| disabled={loading} | ||
| /> | ||
| <button | ||
| type="submit" | ||
| disabled={loading || !input.trim()} | ||
| className="px-4 py-2.5 rounded-xl text-sm font-medium transition-all hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed active:scale-95" | ||
| style={{ background: '#6c63ff', color: 'white' }} | ||
| > | ||
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /> | ||
| </svg> | ||
| </button> |
There was a problem hiding this comment.
Add accessible names for the message controls.
The input relies on placeholder text, and the icon-only submit button has no accessible label. Add an aria-label/visible label so screen reader users can compose and send messages.
♿ Proposed fix
<input
type="text"
+ aria-label={`Message ${agent.name}`}
value={input}
onChange={e => setInput(e.target.value)}
placeholder={`Message ${agent.name}...`}
@@
<button
type="submit"
+ aria-label="Send message"
disabled={loading || !input.trim()}
className="px-4 py-2.5 rounded-xl text-sm font-medium transition-all hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed active:scale-95"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/components/ChatPanel.jsx` around lines 128 - 148, Add accessible
names: give the text input an explicit aria-label (e.g., aria-label={`Message
${agent.name}`} or associate a visible <label>) instead of relying only on
placeholder, and give the submit button an accessible name (e.g.,
aria-label="Send message" or include a visually-hidden text node like <span
className="sr-only">Send</span>) so the icon-only button is announced by screen
readers; update the JSX for the input and the button that use the input,
loading, and agent.name identifiers accordingly.
| def main(): | ||
| print("Hello from repl-nix-workspace!") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
There was a problem hiding this comment.
Remove placeholder main.py.
This looks like a leftover Replit scaffold ("Hello from repl-nix-workspace!") with no role in the AgentOS app — the real entry points are run.py and backend/main.py. Keeping it risks confusion about how to start the app.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@main.py` around lines 1 - 6, Delete the placeholder module main.py (which
only defines the unused main() function and prints "Hello from
repl-nix-workspace!") because the app's real entry points are run.py and
backend/main.py; remove the file and any accidental imports referencing
main.main to avoid confusion about startup behavior.
|
@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding. Phase 1: Review per AGENTS.md
Phase 2: FIX Valid Issues Phase 3: Final Verdict |
|
Claude finished @MervinPraison's task —— View job Final Architecture Review & Bug Fixes ✅Completed comprehensive review and bug fixes for AgentOS implementation. Phase 1: Architecture Review
|
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (2)
backend/main.py (1)
28-34:⚠️ Potential issue | 🟠 MajorRestrict CORS before enabling credentials.
allow_origins=["*"]withallow_credentials=Trueis too permissive. Load explicit frontend origins from config/env, or disable credentials if they are not needed.🔒 Proposed direction
+allowed_origins = [ + origin.strip() + for origin in os.environ.get("AGENTOS_CORS_ORIGINS", "http://localhost:5173").split(",") + if origin.strip() +] + app.add_middleware( CORSMiddleware, - allow_origins=["*"], + allow_origins=allowed_origins, allow_credentials=True,#!/bin/bash # Verify the active CORS configuration. rg -n -C3 'add_middleware|allow_origins|allow_credentials|AGENTOS_CORS_ORIGINS' backend/main.py🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/main.py` around lines 28 - 34, The CORS setup in app.add_middleware using CORSMiddleware currently sets allow_origins=["*"] together with allow_credentials=True which is unsafe; change it to load a specific list of allowed origins from configuration/environment (e.g., parse a CORS_ORIGINS or AGENTOS_CORS_ORIGINS env var into a list) and pass that list to allow_origins, or if you must keep wildcard origins set allow_credentials=False; update the CORSMiddleware call (app.add_middleware, allow_origins, allow_credentials) accordingly and add a fallback/default behavior and validation for the env value.backend/db.py (1)
270-276:⚠️ Potential issue | 🟠 MajorReturn the newest activity entries, not the oldest tail.
entriesare reversed into newest-first order, butactivity[-limit:]keeps the older tail when there are more thanlimititems. Slice from the front instead.🐛 Proposed fix
def list_activity(agent_id: str, limit: int = 100) -> List[Dict]: """Flattened activity across all of an agent's chat entries, newest first, capped.""" + if limit <= 0: + return [] entries = list_history(agent_id) activity: List[Dict] = [] for entry in reversed(entries): activity.extend(entry.get("activity", [])) - return activity[-limit:] + return activity[:limit]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/db.py` around lines 270 - 276, In list_activity, the code reverses entries to produce newest-first activity but then returns activity[-limit:], which slices the oldest tail; change the final slice to take the first limit items from the front (e.g., activity[:limit]) so the function returns the newest activity entries; update the return in list_activity accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/db.py`:
- Around line 124-142: Loop over legacy history must skip orphaned or malformed
rows: before inserting each batch check the agent exists (e.g., query agents
table with agent_id via conn.execute("SELECT 1 FROM agents WHERE id = ?",
(agent_id,)).fetchone()) and skip the entire entries list if no agent; and for
each entry ensure isinstance(entry, dict) and skip non-dict entries; keep using
entry.get(...) and _now() for values. Additionally, guard the insert with a
try/except sqlite3.IntegrityError around conn.execute to continue on unexpected
FK or constraint failures. Update the migration loop that references legacy,
agent_id, entries, entry, conn.execute and _now() accordingly.
- Around line 85-111: The migration assumes json.load(LEGACY_AGENTS_FILE)
returns a list of dicts but may get other JSON shapes; after calling json.load()
(variable legacy) validate that legacy is a list and then for each item ensure
the loop variable a is a dict and contains a usable id before accessing a["id"]
or a.get(...). If the top-level value is not a list, bail out early (return);
when iterating skip any non-dict entries or dicts missing an id. Keep existing
use of ALLOWED_STATUS, _now(), and conn.execute but only call them for validated
dict items to avoid startup crashes.
In `@backend/main.py`:
- Around line 187-199: The history/activity endpoints currently return success
for nonexistent agents; before calling agentdb.list_history,
agentdb.clear_history, or agentdb.list_activity in get_history, clear_history,
and get_activity, first verify the agent exists and raise FastAPI
HTTPException(status_code=404) if not found (use the same existence check used
by get_agent/chat — e.g., call agentdb.get_agent(agent_id) or the project’s
existing agent existence helper and if it returns None raise 404); apply this
check at the start of get_history, clear_history, and get_activity so clients
can distinguish “no history” from “agent not found.”
- Around line 99-132: The current parent_id existence check in create_agent and
update_agent is racy; either move the check into the agentdb write transaction
or catch DB foreign-key errors and translate them to a 400. Concretely: ensure
agentdb.create_agent and agentdb.update_agent perform the parent_id validation
inside the same SQLite transaction (or raise a controlled error), or wrap calls
to agentdb.create_agent/agentdb.update_agent in create_agent/update_agent and
catch sqlite3.IntegrityError (or the DB layer’s FK exception) and raise
HTTPException(status_code=400, detail="parent_id does not exist") so a deleted
parent doesn’t produce a 500.
- Around line 154-166: The code is calling pa_agent.start(body.message) which is
the interactive/streaming entrypoint; replace it with pa_agent.run(body.message)
(Agent.run) for synchronous API usage so you get an Optional[str] return; update
the assignment to result = pa_agent.run(body.message) and ensure response_text =
str(result) (or handle None explicitly) and remove any streaming/interactive
handling tied to start() (references: Agent, pa_agent, start(), run()).
- Around line 41-76: AgentCreate, AgentUpdate and ChatMessage currently accept
empty/invalid values; add Pydantic Field() constraints to each relevant
attribute to enforce valid input: in class AgentCreate and the corresponding
optional fields in AgentUpdate set name=Field(..., min_length=1, max_length=200)
(or similar max you prefer), instructions=Field("", min_length=1,
max_length=5000) and/or instructions: Optional[str]=Field(None, min_length=1,
max_length=5000) in AgentUpdate, message in ChatMessage as Field(...,
min_length=1, max_length=2000), generation as Field(1, ge=1), token_spend as
Field(0, ge=0), and performance_score as Field(None, ge=0.0, le=1.0); import
Field from pydantic and apply the same constraints to both AgentCreate and
AgentUpdate (using Optional[...] with Field for update fields) so invalid/empty,
negative or out-of-range values are rejected before persistence or provider
calls.
---
Duplicate comments:
In `@backend/db.py`:
- Around line 270-276: In list_activity, the code reverses entries to produce
newest-first activity but then returns activity[-limit:], which slices the
oldest tail; change the final slice to take the first limit items from the front
(e.g., activity[:limit]) so the function returns the newest activity entries;
update the return in list_activity accordingly.
In `@backend/main.py`:
- Around line 28-34: The CORS setup in app.add_middleware using CORSMiddleware
currently sets allow_origins=["*"] together with allow_credentials=True which is
unsafe; change it to load a specific list of allowed origins from
configuration/environment (e.g., parse a CORS_ORIGINS or AGENTOS_CORS_ORIGINS
env var into a list) and pass that list to allow_origins, or if you must keep
wildcard origins set allow_credentials=False; update the CORSMiddleware call
(app.add_middleware, allow_origins, allow_credentials) accordingly and add a
fallback/default behavior and validation for the env value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 42dd330d-55e8-4292-a6be-fb1b6ddbd424
📒 Files selected for processing (2)
backend/db.pybackend/main.py
| try: | ||
| with open(LEGACY_AGENTS_FILE) as f: | ||
| legacy = json.load(f) | ||
| except (OSError, json.JSONDecodeError): | ||
| return | ||
| for a in legacy: | ||
| status = a.get("status", "active") | ||
| if status not in ALLOWED_STATUS: | ||
| status = "active" | ||
| created = a.get("created_at") or _now() | ||
| conn.execute( | ||
| """ | ||
| INSERT OR IGNORE INTO agents | ||
| (id, name, instructions, model, status, generation, parent_id, | ||
| performance_score, token_spend, exit_summary, created_at, updated_at) | ||
| VALUES (?, ?, ?, ?, ?, 1, NULL, NULL, 0, NULL, ?, ?) | ||
| """, | ||
| ( | ||
| a["id"], | ||
| a.get("name", "Unnamed"), | ||
| a.get("instructions", ""), | ||
| a.get("llm") or a.get("model") or "gpt-4o-mini", | ||
| status, | ||
| created, | ||
| created, | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Guard the agent JSON migration against unexpected valid JSON shapes.
json.load() can succeed for a dict/string/list with non-dict items, then a.get(...) or a["id"] crashes startup. history migration already validates its top-level shape; do the same here.
🛡️ Proposed hardening
try:
with open(LEGACY_AGENTS_FILE) as f:
legacy = json.load(f)
except (OSError, json.JSONDecodeError):
return
+ if not isinstance(legacy, list):
+ return
for a in legacy:
+ if not isinstance(a, dict) or "id" not in a:
+ continue
status = a.get("status", "active")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/db.py` around lines 85 - 111, The migration assumes
json.load(LEGACY_AGENTS_FILE) returns a list of dicts but may get other JSON
shapes; after calling json.load() (variable legacy) validate that legacy is a
list and then for each item ensure the loop variable a is a dict and contains a
usable id before accessing a["id"] or a.get(...). If the top-level value is not
a list, bail out early (return); when iterating skip any non-dict entries or
dicts missing an id. Keep existing use of ALLOWED_STATUS, _now(), and
conn.execute but only call them for validated dict items to avoid startup
crashes.
| for agent_id, entries in legacy.items(): | ||
| if not isinstance(entries, list): | ||
| continue | ||
| for entry in entries: | ||
| conn.execute( | ||
| """ | ||
| INSERT OR IGNORE INTO chat_history | ||
| (id, agent_id, user_message, agent_response, activity, timestamp) | ||
| VALUES (?, ?, ?, ?, ?, ?) | ||
| """, | ||
| ( | ||
| entry.get("id") or os.urandom(4).hex(), | ||
| agent_id, | ||
| entry.get("user_message", ""), | ||
| entry.get("agent_response", ""), | ||
| json.dumps(entry.get("activity", [])), | ||
| entry.get("timestamp") or _now(), | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Skip orphaned or malformed legacy history rows during migration.
With PRAGMA foreign_keys = ON, a stale agent_id in history_store.json raises sqlite3.IntegrityError and aborts init_db(). Validate the agent exists and each entry is a dict before inserting.
🛡️ Proposed migration guard
for agent_id, entries in legacy.items():
if not isinstance(entries, list):
continue
+ if not conn.execute("SELECT 1 FROM agents WHERE id = ?", (agent_id,)).fetchone():
+ continue
for entry in entries:
+ if not isinstance(entry, dict):
+ continue
conn.execute(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/db.py` around lines 124 - 142, Loop over legacy history must skip
orphaned or malformed rows: before inserting each batch check the agent exists
(e.g., query agents table with agent_id via conn.execute("SELECT 1 FROM agents
WHERE id = ?", (agent_id,)).fetchone()) and skip the entire entries list if no
agent; and for each entry ensure isinstance(entry, dict) and skip non-dict
entries; keep using entry.get(...) and _now() for values. Additionally, guard
the insert with a try/except sqlite3.IntegrityError around conn.execute to
continue on unexpected FK or constraint failures. Update the migration loop that
references legacy, agent_id, entries, entry, conn.execute and _now()
accordingly.
| class AgentCreate(BaseModel): | ||
| # Accept unknown legacy fields (role, tools, connections) without error. | ||
| # `protected_namespaces=()` silences Pydantic v2 warnings about the | ||
| # `model` field name colliding with its protected namespace. | ||
| model_config = ConfigDict(extra="ignore", protected_namespaces=()) | ||
|
|
||
| name: str | ||
| instructions: str = "" | ||
| model: Optional[str] = None | ||
| llm: Optional[str] = None # legacy alias for `model` | ||
| status: str = "active" | ||
| generation: int = 1 | ||
| parent_id: Optional[str] = None | ||
| performance_score: Optional[float] = None | ||
| token_spend: int = 0 | ||
| exit_summary: Optional[str] = None | ||
|
|
||
|
|
||
| class AgentUpdate(BaseModel): | ||
| model_config = ConfigDict(extra="ignore", protected_namespaces=()) | ||
|
|
||
| name: Optional[str] = None | ||
| instructions: Optional[str] = None | ||
| model: Optional[str] = None | ||
| llm: Optional[str] = None # legacy alias for `model` | ||
| status: Optional[str] = None | ||
| generation: Optional[int] = None | ||
| parent_id: Optional[str] = None | ||
| performance_score: Optional[float] = None | ||
| token_spend: Optional[int] = None | ||
| exit_summary: Optional[str] = None | ||
|
|
||
|
|
||
| class ChatMessage(BaseModel): | ||
| message: str | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether these request models already have bounds elsewhere.
rg -n -C3 'class AgentCreate|class AgentUpdate|class ChatMessage|Field\(|generation|token_spend|performance_score|message:' backend/main.pyRepository: MervinPraison/PraisonAI
Length of output: 1944
🏁 Script executed:
#!/bin/bash
# Check if there are any field validators in the file
rg -n "field_validator|validator|@" backend/main.py | head -30Repository: MervinPraison/PraisonAI
Length of output: 469
🏁 Script executed:
#!/bin/bash
# Check the full context of backend/main.py around these classes
wc -l backend/main.pyRepository: MervinPraison/PraisonAI
Length of output: 86
🏁 Script executed:
#!/bin/bash
# Search for any Pydantic Field imports or usage
rg -n "from pydantic|import.*Field" backend/main.pyRepository: MervinPraison/PraisonAI
Length of output: 112
🏁 Script executed:
#!/bin/bash
# Check the POST /agents endpoint to confirm it uses AgentCreate
sed -n '99,118p' backend/main.pyRepository: MervinPraison/PraisonAI
Length of output: 802
🏁 Script executed:
#!/bin/bash
# Check the PUT /agents endpoint to confirm it uses AgentUpdate
sed -n '119,137p' backend/main.pyRepository: MervinPraison/PraisonAI
Length of output: 751
🏁 Script executed:
#!/bin/bash
# Check the POST /agents/{agent_id}/chat endpoint to confirm it uses ChatMessage
sed -n '145,186p' backend/main.pyRepository: MervinPraison/PraisonAI
Length of output: 1617
🏁 Script executed:
#!/bin/bash
# Check if there are database schema definitions or constraints elsewhere
find . -name "*db*" -o -name "*database*" -o -name "*schema*" | grep -E "\.(py|sql)$" | head -20Repository: MervinPraison/PraisonAI
Length of output: 991
🏁 Script executed:
#!/bin/bash
# Check the backend/db.py file for any constraints
cat -n backend/db.pyRepository: MervinPraison/PraisonAI
Length of output: 10516
Add Pydantic field constraints to prevent invalid user input.
name, message, instructions, generation, token_spend, and performance_score currently accept empty, negative, or out-of-range values that are persisted and sent to the agent provider, risking bad state and cost abuse. Database schema has no constraints either. Add Pydantic Field() validators with bounds:
Proposed validation
-from pydantic import BaseModel, ConfigDict
+from pydantic import BaseModel, ConfigDict, Field
@@
- name: str
- instructions: str = ""
+ name: str = Field(min_length=1, max_length=120)
+ instructions: str = Field(default="", max_length=20_000)
@@
- generation: int = 1
+ generation: int = Field(default=1, ge=1)
@@
- performance_score: Optional[float] = None
- token_spend: int = 0
+ performance_score: Optional[float] = Field(default=None, ge=0.0, le=1.0)
+ token_spend: int = Field(default=0, ge=0)
@@
- name: Optional[str] = None
- instructions: Optional[str] = None
+ name: Optional[str] = Field(default=None, min_length=1, max_length=120)
+ instructions: Optional[str] = Field(default=None, max_length=20_000)
@@
- generation: Optional[int] = None
- performance_score: Optional[float] = None
- token_spend: Optional[int] = None
+ generation: Optional[int] = Field(default=None, ge=1)
+ performance_score: Optional[float] = Field(default=None, ge=0.0, le=1.0)
+ token_spend: Optional[int] = Field(default=None, ge=0)
@@
class ChatMessage(BaseModel):
- message: str
+ message: str = Field(min_length=1, max_length=20_000)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/main.py` around lines 41 - 76, AgentCreate, AgentUpdate and
ChatMessage currently accept empty/invalid values; add Pydantic Field()
constraints to each relevant attribute to enforce valid input: in class
AgentCreate and the corresponding optional fields in AgentUpdate set
name=Field(..., min_length=1, max_length=200) (or similar max you prefer),
instructions=Field("", min_length=1, max_length=5000) and/or instructions:
Optional[str]=Field(None, min_length=1, max_length=5000) in AgentUpdate, message
in ChatMessage as Field(..., min_length=1, max_length=2000), generation as
Field(1, ge=1), token_spend as Field(0, ge=0), and performance_score as
Field(None, ge=0.0, le=1.0); import Field from pydantic and apply the same
constraints to both AgentCreate and AgentUpdate (using Optional[...] with Field
for update fields) so invalid/empty, negative or out-of-range values are
rejected before persistence or provider calls.
| @app.post("/agents", status_code=201) | ||
| def create_agent(data: AgentCreate): | ||
| _validate_status(data.status) | ||
| if data.parent_id and not agentdb.get_agent(data.parent_id): | ||
| raise HTTPException(status_code=400, detail="parent_id does not exist") | ||
| record = { | ||
| "id": f"agent-{uuid.uuid4().hex[:8]}", | ||
| "name": data.name, | ||
| "instructions": data.instructions, | ||
| "model": data.model or data.llm or "gpt-4o-mini", | ||
| "status": data.status, | ||
| "generation": data.generation, | ||
| "parent_id": data.parent_id, | ||
| "performance_score": data.performance_score, | ||
| "token_spend": data.token_spend, | ||
| "exit_summary": data.exit_summary, | ||
| } | ||
| return agentdb.create_agent(record) | ||
|
|
||
|
|
||
| @app.put("/agents/{agent_id}") | ||
| def update_agent(agent_id: str, data: AgentUpdate): | ||
| fields = data.model_dump(exclude_none=True) | ||
| # Legacy `llm` alias -> `model` | ||
| if "llm" in fields and "model" not in fields: | ||
| fields["model"] = fields["llm"] | ||
| fields.pop("llm", None) | ||
|
|
||
| if "status" in fields: | ||
| _validate_status(fields["status"]) | ||
| if fields.get("parent_id") and not agentdb.get_agent(fields["parent_id"]): | ||
| raise HTTPException(status_code=400, detail="parent_id does not exist") | ||
|
|
||
| updated = agentdb.update_agent(agent_id, fields) |
There was a problem hiding this comment.
Handle parent_id validation atomically with the write.
The existence check happens before create_agent()/update_agent(). If the parent is deleted between the check and write, SQLite raises a FK error and the API returns a 500. Move the check into the DB transaction or translate sqlite3.IntegrityError to a 400.
🐛 Proposed endpoint-side fallback
+import sqlite3
@@
- return agentdb.create_agent(record)
+ try:
+ return agentdb.create_agent(record)
+ except sqlite3.IntegrityError as exc:
+ raise HTTPException(status_code=400, detail="parent_id does not exist") from exc
@@
- updated = agentdb.update_agent(agent_id, fields)
+ try:
+ updated = agentdb.update_agent(agent_id, fields)
+ except sqlite3.IntegrityError as exc:
+ raise HTTPException(status_code=400, detail="parent_id does not exist") from exc📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @app.post("/agents", status_code=201) | |
| def create_agent(data: AgentCreate): | |
| _validate_status(data.status) | |
| if data.parent_id and not agentdb.get_agent(data.parent_id): | |
| raise HTTPException(status_code=400, detail="parent_id does not exist") | |
| record = { | |
| "id": f"agent-{uuid.uuid4().hex[:8]}", | |
| "name": data.name, | |
| "instructions": data.instructions, | |
| "model": data.model or data.llm or "gpt-4o-mini", | |
| "status": data.status, | |
| "generation": data.generation, | |
| "parent_id": data.parent_id, | |
| "performance_score": data.performance_score, | |
| "token_spend": data.token_spend, | |
| "exit_summary": data.exit_summary, | |
| } | |
| return agentdb.create_agent(record) | |
| @app.put("/agents/{agent_id}") | |
| def update_agent(agent_id: str, data: AgentUpdate): | |
| fields = data.model_dump(exclude_none=True) | |
| # Legacy `llm` alias -> `model` | |
| if "llm" in fields and "model" not in fields: | |
| fields["model"] = fields["llm"] | |
| fields.pop("llm", None) | |
| if "status" in fields: | |
| _validate_status(fields["status"]) | |
| if fields.get("parent_id") and not agentdb.get_agent(fields["parent_id"]): | |
| raise HTTPException(status_code=400, detail="parent_id does not exist") | |
| updated = agentdb.update_agent(agent_id, fields) | |
| import sqlite3 | |
| `@app.post`("/agents", status_code=201) | |
| def create_agent(data: AgentCreate): | |
| _validate_status(data.status) | |
| if data.parent_id and not agentdb.get_agent(data.parent_id): | |
| raise HTTPException(status_code=400, detail="parent_id does not exist") | |
| record = { | |
| "id": f"agent-{uuid.uuid4().hex[:8]}", | |
| "name": data.name, | |
| "instructions": data.instructions, | |
| "model": data.model or data.llm or "gpt-4o-mini", | |
| "status": data.status, | |
| "generation": data.generation, | |
| "parent_id": data.parent_id, | |
| "performance_score": data.performance_score, | |
| "token_spend": data.token_spend, | |
| "exit_summary": data.exit_summary, | |
| } | |
| try: | |
| return agentdb.create_agent(record) | |
| except sqlite3.IntegrityError as exc: | |
| raise HTTPException(status_code=400, detail="parent_id does not exist") from exc | |
| `@app.put`("/agents/{agent_id}") | |
| def update_agent(agent_id: str, data: AgentUpdate): | |
| fields = data.model_dump(exclude_none=True) | |
| # Legacy `llm` alias -> `model` | |
| if "llm" in fields and "model" not in fields: | |
| fields["model"] = fields["llm"] | |
| fields.pop("llm", None) | |
| if "status" in fields: | |
| _validate_status(fields["status"]) | |
| if fields.get("parent_id") and not agentdb.get_agent(fields["parent_id"]): | |
| raise HTTPException(status_code=400, detail="parent_id does not exist") | |
| try: | |
| updated = agentdb.update_agent(agent_id, fields) | |
| except sqlite3.IntegrityError as exc: | |
| raise HTTPException(status_code=400, detail="parent_id does not exist") from exc | |
| return updated |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/main.py` around lines 99 - 132, The current parent_id existence check
in create_agent and update_agent is racy; either move the check into the agentdb
write transaction or catch DB foreign-key errors and translate them to a 400.
Concretely: ensure agentdb.create_agent and agentdb.update_agent perform the
parent_id validation inside the same SQLite transaction (or raise a controlled
error), or wrap calls to agentdb.create_agent/agentdb.update_agent in
create_agent/update_agent and catch sqlite3.IntegrityError (or the DB layer’s FK
exception) and raise HTTPException(status_code=400, detail="parent_id does not
exist") so a deleted parent doesn’t produce a 500.
| api_key = os.environ.get("OPENAI_API_KEY", "") | ||
| if api_key: | ||
| try: | ||
| from praisonaiagents import Agent | ||
| activity.append(_log("tool", f"Initializing {agent['model']} agent")) | ||
| pa_agent = Agent( | ||
| name=agent["name"], | ||
| instructions=agent["instructions"], | ||
| llm=agent["model"], | ||
| ) | ||
| activity.append(_log("tool", "Running agent inference")) | ||
| result = pa_agent.start(body.message) | ||
| response_text = str(result) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify Agent's sync/async entrypoints before changing this call.
rg -n -C4 'class Agent|async def start|def start|def run' src/praisonai-agents/praisonaiagents/agent/agent.pyRepository: MervinPraison/PraisonAI
Length of output: 1802
🏁 Script executed:
# Search for run and start method definitions in Agent class and its mixins
rg -n "^\s*(async\s+)?def (run|start)\(" src/praisonai-agents/praisonaiagents/agent/Repository: MervinPraison/PraisonAI
Length of output: 1003
🏁 Script executed:
# Also check the mixins directory to see what's inherited
fd -t f "\.py$" src/praisonai-agents/praisonaiagents/agent/ | head -20Repository: MervinPraison/PraisonAI
Length of output: 1242
🏁 Script executed:
# Look at the Agent class definition and its parent classes more carefully
sed -n '255,280p' src/praisonai-agents/praisonaiagents/agent/agent.pyRepository: MervinPraison/PraisonAI
Length of output: 1315
🏁 Script executed:
# Check the run() method implementation
sed -n '223,260p' src/praisonai-agents/praisonaiagents/agent/execution_mixin.pyRepository: MervinPraison/PraisonAI
Length of output: 1640
🏁 Script executed:
# Check the start() method implementation
sed -n '598,650p' src/praisonai-agents/praisonaiagents/agent/execution_mixin.pyRepository: MervinPraison/PraisonAI
Length of output: 2696
🏁 Script executed:
# Check the rest of start() implementation to see the return logic
sed -n '598,700p' src/praisonai-agents/praisonaiagents/agent/execution_mixin.pyRepository: MervinPraison/PraisonAI
Length of output: 5220
Use run() instead of start() for API endpoints.
The Agent class provides both run() and start() as synchronous entry points. The run() method is designed for programmatic/API use (production pipelines, API endpoints, automated scripts) and always returns Optional[str]. The start() method is designed for interactive terminal usage and can stream output. Since this is a synchronous API route, use run() to get consistent string responses without verbose output.
Suggested fix
activity.append(_log("tool", "Running agent inference"))
- result = pa_agent.start(body.message)
+ result = pa_agent.run(body.message)
response_text = str(result)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/main.py` around lines 154 - 166, The code is calling
pa_agent.start(body.message) which is the interactive/streaming entrypoint;
replace it with pa_agent.run(body.message) (Agent.run) for synchronous API usage
so you get an Optional[str] return; update the assignment to result =
pa_agent.run(body.message) and ensure response_text = str(result) (or handle
None explicitly) and remove any streaming/interactive handling tied to start()
(references: Agent, pa_agent, start(), run()).
| @app.get("/agents/{agent_id}/history") | ||
| def get_history(agent_id: str): | ||
| return agentdb.list_history(agent_id) | ||
|
|
||
|
|
||
| @app.delete("/agents/{agent_id}/history", status_code=204) | ||
| def clear_history(agent_id: str): | ||
| agentdb.clear_history(agent_id) | ||
|
|
||
|
|
||
| @app.get("/agents/{agent_id}/activity") | ||
| def get_activity(agent_id: str): | ||
| return agentdb.list_activity(agent_id, limit=100) |
There was a problem hiding this comment.
Return 404 for history/activity requests on missing agents.
GET/DELETE history and GET activity currently succeed for nonexistent agents, unlike get_agent and chat. Check existence first so clients can distinguish “no history” from “agent not found.”
🐛 Proposed consistency fix
`@app.get`("/agents/{agent_id}/history")
def get_history(agent_id: str):
+ if not agentdb.get_agent(agent_id):
+ raise HTTPException(status_code=404, detail="Agent not found")
return agentdb.list_history(agent_id)
@@
`@app.delete`("/agents/{agent_id}/history", status_code=204)
def clear_history(agent_id: str):
+ if not agentdb.get_agent(agent_id):
+ raise HTTPException(status_code=404, detail="Agent not found")
agentdb.clear_history(agent_id)
@@
`@app.get`("/agents/{agent_id}/activity")
def get_activity(agent_id: str):
+ if not agentdb.get_agent(agent_id):
+ raise HTTPException(status_code=404, detail="Agent not found")
return agentdb.list_activity(agent_id, limit=100)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/main.py` around lines 187 - 199, The history/activity endpoints
currently return success for nonexistent agents; before calling
agentdb.list_history, agentdb.clear_history, or agentdb.list_activity in
get_history, clear_history, and get_activity, first verify the agent exists and
raise FastAPI HTTPException(status_code=404) if not found (use the same
existence check used by get_agent/chat — e.g., call agentdb.get_agent(agent_id)
or the project’s existing agent existence helper and if it returns None raise
404); apply this check at the start of get_history, clear_history, and
get_activity so clients can distinguish “no history” from “agent not found.”
- AgentForm: status options now match backend ALLOWED_STATUS (active/auditing/decommissioned instead of active/inactive). Drop role/tools/connections fields since they are no longer persisted. Submit `model` directly (backend still accepts `llm`). - AgentDetail: stop dereferencing agent.tools/connections (undefined after schema change — was crashing the Details tab). Replace those sections with new-schema fields (generation, token_spend, performance_score, parent_id, exit_summary) and swap the role subtitle for the model name. Three-state status color mapping. - App.jsx: add res.ok guards to chat/clear/save/delete handlers so backend errors no longer silently corrupt local state; surface the failure to the user. Also clear activity state when history is cleared so ActivityLog doesn't show stale entries.
App.jsx header, Sidebar.jsx subtitle, and ChatPanel.jsx empty-state all read agent.role which is not in the schema and renders blank. Switch to agent.model throughout. Also extend status dot colours to the three-value set (active=green, auditing=amber, decommissioned=grey) in App.jsx and Sidebar.jsx.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
frontend/src/App.jsx (3)
175-179:⚠️ Potential issue | 🟡 MinorReplace the removed
rolefield in the header.The current agent schema no longer persists
role, so this renders a dangling subtitle for newly created agents. Usemodelor another persisted field instead. This is related to the earlier schema-alignment finding around droppedrole.🧹 Proposed schema-aligned display
- <span className="text-sm text-gray-400 truncate hidden sm:block">— {selectedAgent.role}</span> + <span className="text-sm text-gray-400 truncate hidden sm:block">— {selectedAgent.model}</span>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.jsx` around lines 175 - 179, The UI is rendering a non-persisted field selectedAgent.role; replace that dangling subtitle with a persisted field (e.g., selectedAgent.model) so new agents show correct data. Locate the header block that references selectedAgent (the JSX containing the rounded status dot, selectedAgent.name and the subtitle span) and change the subtitle span to display selectedAgent.model (or another persisted property) and update any aria/tooltip text if present to reflect "model" instead of "role".
32-55:⚠️ Potential issue | 🟠 MajorGuard agent-scoped async updates against stale responses.
History/activity fetches and chat sends can resolve after the user switches agents, then overwrite or append data for the wrong selected agent. This was already flagged for history/activity and is still present.
🛡️ Proposed direction
-import { useState, useEffect, useCallback } from 'react' +import { useState, useEffect, useCallback, useRef } from 'react' @@ const [loading, setLoading] = useState(false) + const selectedIdRef = useRef(selectedId) + + useEffect(() => { + selectedIdRef.current = selectedId + }, [selectedId]) @@ const fetchHistory = useCallback(async (id) => { if (!id) return try { const res = await fetch(`${API}/agents/${id}/history`) - setHistory(await res.json()) + if (!res.ok) throw new Error(`Fetch history failed: ${res.status}`) + const data = await res.json() + if (selectedIdRef.current === id) setHistory(data) } catch (e) { console.error(e) } }, []) @@ const fetchActivity = useCallback(async (id) => { if (!id) return try { const res = await fetch(`${API}/agents/${id}/activity`) - setActivity(await res.json()) + if (!res.ok) throw new Error(`Fetch activity failed: ${res.status}`) + const data = await res.json() + if (selectedIdRef.current === id) setActivity(data) } catch (e) { console.error(e) } }, []) @@ const handleSendMessage = async (message) => { if (!selectedId) return + const agentId = selectedId setLoading(true) try { - const res = await fetch(`${API}/agents/${selectedId}/chat`, { + const res = await fetch(`${API}/agents/${agentId}/chat`, { @@ if (!res.ok) { await reportError(res, 'Send message'); return } const entry = await res.json() + if (selectedIdRef.current !== agentId) return setHistory(prev => [...prev, entry]) - setActivity(prev => [...entry.activity, ...prev]) + setActivity(prev => [...(Array.isArray(entry.activity) ? entry.activity : []), ...prev])Also applies to: 70-82
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.jsx` around lines 32 - 55, fetchHistory and fetchActivity can resolve after the user switches agents and then update state for the wrong agent; protect these functions by cancelling or ignoring stale responses: create an AbortController (or a local request token) inside fetchHistory and fetchActivity, pass its signal to fetch, and when the effect that calls them re-runs or when selectedId changes abort the previous controller (or compare the token against current selectedId before calling setHistory/setActivity). Apply the same pattern to the chat send logic (the function that appends messages) so responses are only applied if the response's agent id/token matches the current selectedId (or the request was not aborted).
91-127:⚠️ Potential issue | 🟠 MajorCatch network failures in mutation handlers.
fetchrejections in clear/save/delete currently escape these handlers;AgentFormdoes not await/catchonSave, so users get no error message on network failure. Save/delete try-catch was already requested previously and is still missing.🛡️ Proposed pattern
const handleClearHistory = async () => { if (!selectedId) return - const res = await fetch(`${API}/agents/${selectedId}/history`, { method: 'DELETE' }) - if (!res.ok) { await reportError(res, 'Clear history'); return } - setHistory([]) - setActivity([]) + try { + const res = await fetch(`${API}/agents/${selectedId}/history`, { method: 'DELETE' }) + if (!res.ok) { await reportError(res, 'Clear history'); return } + setHistory([]) + setActivity([]) + } catch (e) { + console.error(e) + window.alert('Network error while clearing history.') + } } const handleSaveAgent = async (data) => { - if (editingAgent) { + try { + if (editingAgent) { const res = await fetch(`${API}/agents/${editingAgent.id}`, { @@ setSelectedId(created.id) } setShowForm(false) setEditingAgent(null) + } catch (e) { + console.error(e) + window.alert('Network error while saving agent.') + } } const handleDeleteAgent = async (id) => { if (!window.confirm('Delete this agent?')) return - const res = await fetch(`${API}/agents/${id}`, { method: 'DELETE' }) - if (!res.ok) { await reportError(res, 'Delete agent'); return } - setAgents(prev => prev.filter(a => a.id !== id)) - if (selectedId === id) { - const remaining = agents.filter(a => a.id !== id) - setSelectedId(remaining.length > 0 ? remaining[0].id : null) + try { + const res = await fetch(`${API}/agents/${id}`, { method: 'DELETE' }) + if (!res.ok) { await reportError(res, 'Delete agent'); return } + setAgents(prev => prev.filter(a => a.id !== id)) + if (selectedId === id) { + const remaining = agents.filter(a => a.id !== id) + setSelectedId(remaining.length > 0 ? remaining[0].id : null) + } + } catch (e) { + console.error(e) + window.alert('Network error while deleting agent.') } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.jsx` around lines 91 - 127, Wrap all network mutations in try/catch to prevent unhandled fetch rejections: add a try block around the bodies of handleClearHistory, handleSaveAgent, and handleDeleteAgent and catch network errors to call reportError(e, '<operation>') (or a generic error message) and return early; keep existing res.ok handling inside the try. Also ensure the caller of AgentForm's onSave awaits the promise and handles rejections so UI shows errors (i.e., have AgentForm's submit handler await props.onSave(...) and catch/report any thrown errors). Use the function names handleClearHistory, handleSaveAgent, handleDeleteAgent, and the AgentForm onSave prop to locate the changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/App.jsx`:
- Around line 21-30: The fetchAgents callback must validate the HTTP response
and payload before updating state: check res.ok and handle non-2xx responses
(e.g., throw or setAgents([]) and log), then parse JSON and ensure the parsed
value is an array (use Array.isArray(data)) before calling setAgents(data); only
setSelectedId(data[0].id) when data is a non-empty array. Update the fetchAgents
function to guard on res.ok and Array.isArray(data) to avoid storing error
objects in agents and breaking children.
In `@frontend/src/components/AgentForm.jsx`:
- Around line 85-99: Replace the custom clickable divs with native radio inputs
so keyboard users can tab and change selection: inside the Field for "Status"
iterate STATUSES to render an input type="radio" with name="status", value={s},
checked={form.status === s} and onChange={() => set('status', s)}, visually
style the radio (or hide default appearance) but keep it in the DOM for
accessibility, and ensure the label wraps the input and the visible text so
form.status, set('status', s) and STATUSES are used as the state hookup.
- Around line 30-40: The modal markup in AgentForm.jsx lacks dialog semantics
and the close button is icon-only without an accessible name; update the outer
container div to include role="dialog", aria-modal="true", and link it to the
header by adding an id to the <h2> (e.g., agentFormTitle) and aria-labelledby on
the dialog container, and give the close button an accessible name via
aria-label="Close" (or aria-labelledby) while keeping onCancel as the handler;
ensure these changes are applied around the elements that contain handleSubmit,
the header <h2>, and the close button so screen readers properly announce the
dialog and the close control.
---
Duplicate comments:
In `@frontend/src/App.jsx`:
- Around line 175-179: The UI is rendering a non-persisted field
selectedAgent.role; replace that dangling subtitle with a persisted field (e.g.,
selectedAgent.model) so new agents show correct data. Locate the header block
that references selectedAgent (the JSX containing the rounded status dot,
selectedAgent.name and the subtitle span) and change the subtitle span to
display selectedAgent.model (or another persisted property) and update any
aria/tooltip text if present to reflect "model" instead of "role".
- Around line 32-55: fetchHistory and fetchActivity can resolve after the user
switches agents and then update state for the wrong agent; protect these
functions by cancelling or ignoring stale responses: create an AbortController
(or a local request token) inside fetchHistory and fetchActivity, pass its
signal to fetch, and when the effect that calls them re-runs or when selectedId
changes abort the previous controller (or compare the token against current
selectedId before calling setHistory/setActivity). Apply the same pattern to the
chat send logic (the function that appends messages) so responses are only
applied if the response's agent id/token matches the current selectedId (or the
request was not aborted).
- Around line 91-127: Wrap all network mutations in try/catch to prevent
unhandled fetch rejections: add a try block around the bodies of
handleClearHistory, handleSaveAgent, and handleDeleteAgent and catch network
errors to call reportError(e, '<operation>') (or a generic error message) and
return early; keep existing res.ok handling inside the try. Also ensure the
caller of AgentForm's onSave awaits the promise and handles rejections so UI
shows errors (i.e., have AgentForm's submit handler await props.onSave(...) and
catch/report any thrown errors). Use the function names handleClearHistory,
handleSaveAgent, handleDeleteAgent, and the AgentForm onSave prop to locate the
changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7c9ec802-9e59-4261-b0ce-057c481aed78
📒 Files selected for processing (3)
frontend/src/App.jsxfrontend/src/components/AgentDetail.jsxfrontend/src/components/AgentForm.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/AgentDetail.jsx
App.jsx: - Add selectedIdRef to guard fetchHistory, fetchActivity, and chat against stale responses when the user switches agents mid-flight. - Capture agentId before await in handleSendMessage so the response is discarded if the selection changed. - Wrap handleClearHistory, handleSaveAgent, handleDeleteAgent in try/catch so network failures surface an alert instead of silently swallowing the error. - Validate fetchAgents response (res.ok + Array.isArray) before updating state. - Guard entry.activity spread with Array.isArray fallback. AgentForm.jsx: - Add role="dialog", aria-modal="true", aria-labelledby to the modal wrapper and id to the title so screen readers announce the dialog. - Add aria-label="Close agent form" to the icon-only close button. - Replace clickable divs for status with native sr-only radio inputs so keyboard users can tab and change selection.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/App.jsx`:
- Around line 91-133: The three handlers handleClearHistory, handleSaveAgent,
and handleDeleteAgent should wrap their fetch calls in a try/catch (and finally
where appropriate) like handleSendMessage does: move the fetch+response handling
into a try block, catch fetch/network errors and call await
reportError(errOrResponse, '<action name>') so the user sees feedback, and use
finally to ensure UI state (e.g., setShowForm(false), setEditingAgent(null), or
closing modals/clearing loading flags) is not left stranded; preserve existing
success branches (setHistory, setAgents, setSelectedId) and ensure any thrown
errors do not produce unhandled promise rejections.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 85710a38-8a17-497e-8b31-e20dc8660328
📒 Files selected for processing (3)
frontend/src/App.jsxfrontend/src/components/ChatPanel.jsxfrontend/src/components/Sidebar.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/Sidebar.jsx
| llm=agent["model"], | ||
| ) | ||
| activity.append(_log("tool", "Running agent inference")) |
There was a problem hiding this comment.
praisonaiagents not listed as a dependency
The chat endpoint imports praisonaiagents at runtime:
from praisonaiagents import AgentThis package is not listed in pyproject.toml. Anyone who installs the project from its declared dependencies will hit an ImportError the first time they send a message with a real API key set. The demo mode codepath hides this during casual testing, making the bug easy to miss until production use.
Add praisonaiagents to pyproject.toml's dependencies list.
backend/db.py: list_activity was returning the oldest 100 entries instead of newest because activity[-limit:] takes the tail of a newest-first list. Switch to activity[:limit] and add an early-exit once the cap is reached. pyproject.toml: remove unused deps (aiosqlite, sqlalchemy, httpx, passlib, pyjwt, pydantic[email] extra) and add the missing praisonaiagents runtime dependency that the chat endpoint imports.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
frontend/src/App.jsx (3)
60-65: Missing deps will triggerreact-hooks/exhaustive-deps.This effect uses
fetchHistory/fetchActivitybut lists only[selectedId]. Both callbacks are stable (emptyuseCallbackdeps) so behavior is correct, but the lint warning is noisy. Add them to the dep array (or inline the fetches) to silence it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.jsx` around lines 60 - 65, The effect in App.jsx depends on fetchHistory and fetchActivity but only lists selectedId, causing an exhaustive-deps lint warning; update the useEffect dependency array to include fetchHistory and fetchActivity (i.e., useEffect(..., [selectedId, fetchHistory, fetchActivity])) or alternatively inline the fetch calls instead of using the callbacks so the linter is satisfied; ensure you keep the existing conditional on selectedId and that both callbacks remain stable.
72-78: Replacewindow.alertwith non-blocking UI feedback.
reportErrorand the catch handlers throughout (handleSendMessage,handleClearHistory,handleSaveAgent,handleDeleteAgent) all callwindow.alert, which is modal, blocks the event loop, and is jarring on mobile. For a polished dashboard, route errors through a toast component or inline banner instead. Functionally fine for now — flagging as a UX follow-up.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.jsx` around lines 72 - 78, Replace the blocking window.alert calls with a non-blocking UI feedback mechanism: update reportError to call a toast/banner helper instead of window.alert and remove any other window.alert usages in handleSendMessage, handleClearHistory, handleSaveAgent, and handleDeleteAgent; if a Toast component already exists, call its show method (or set a top-level error state to render an inline banner), otherwise add a small showToast helper that accepts the message and severity and invoke that from reportError and the listed handlers while keeping console.error for diagnostics.
25-36:fetchAgentsdepends onselectedIdbut its caller never re-runs.
fetchAgentsis recreated on everyselectedIdchange (because of the dep), yet the only invocation site (Line 58) is auseEffect(..., [])that captures the initial closure. Net effect: the dep does nothing useful and ESLint'sreact-hooks/exhaustive-depswill flag the empty array on Line 58.Drop the
selectedIddep by reading via the ref you already have:const fetchAgents = useCallback(async () => { try { const res = await fetch(`${API}/agents`) if (!res.ok) throw new Error(`Fetch agents failed: ${res.status}`) const data = await res.json() if (!Array.isArray(data)) throw new Error('Expected array of agents') setAgents(data) - if (!selectedId && data.length > 0) setSelectedId(data[0].id) + if (!selectedIdRef.current && data.length > 0) setSelectedId(data[0].id) } catch (e) { console.error('Failed to fetch agents', e) } - }, [selectedId]) + }, [])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/App.jsx` around lines 25 - 36, The fetchAgents useCallback should not depend on selectedId because its sole caller is inside a useEffect with an empty dep array; remove selectedId from fetchAgents' dependency list and instead read the current value from the existing ref (e.g., selectedIdRef.current) inside fetchAgents when deciding whether to setSelectedId; also ensure wherever setSelectedId is called the ref is kept in sync so selectedIdRef.current reflects updates. This makes fetchAgents stable (no selectedId in the deps) and avoids the stale-closure / ESLint react-hooks/exhaustive-deps issue.frontend/src/components/AgentForm.jsx (2)
30-47: Modal lacks Escape-key dismiss and backdrop-click close.The dialog has proper ARIA semantics now, but two common modal UX expectations are still missing:
- Pressing
Escapedoes not triggeronCancel.- Clicking the backdrop (outside the inner panel) does not dismiss.
Since
App.jsxunmounts the modal viashowForm, both can be wired with a smalluseEffectfor the keydown listener and anonClickon the outer overlay that callsonCancelonly whene.target === e.currentTarget. Optional but worth doing alongside the recent a11y polish.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/AgentForm.jsx` around lines 30 - 47, Add Escape-key and backdrop-click dismissal to the AgentForm modal: attach an onClick handler to the outer overlay div (the element with role="dialog" / className="fixed inset-0...") that calls onCancel only when e.target === e.currentTarget (so clicks inside the inner panel/form do not close it), and add a useEffect in the AgentForm component that listens for keydown, calls onCancel when event.key === 'Escape', and cleans up the listener on unmount; keep existing handleSubmit and inner form behavior unchanged.
13-19: Form state doesn't re-sync if theagentprop changes.
useStateinitializers run only on mount, so reusing this component instance with a differentagent(e.g., switching from "edit A" to "edit B" without unmounting) would keep stale field values. This is currently safe becauseApp.jsxunmounts the form viashowForm, but it is brittle to future refactors. Consider keying the modal onagent?.idat the call site, or syncing viauseEffect:+ useEffect(() => { + setForm({ + name: agent?.name || '', + instructions: agent?.instructions || '', + model: agent?.model || agent?.llm || 'gpt-4o-mini', + status: agent?.status || 'active', + }) + }, [agent?.id])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/AgentForm.jsx` around lines 13 - 19, The AgentForm component's local state (form, setForm) is only initialized once via useState and won't update when the agent prop changes; update AgentForm to resync form when agent changes by adding a useEffect that watches agent (or agent?.id) and calls setForm({ name: agent?.name || '', instructions: agent?.instructions || '', model: agent?.model || agent?.llm || 'gpt-4o-mini', status: agent?.status || 'active' }); alternatively, ensure the parent keys/unmounts the modal by agent?.id to force remounts — pick one approach and apply it to AgentForm so editing a different agent updates the form fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/components/AgentForm.jsx`:
- Around line 3-11: LLM_MODELS is a hardcoded allowlist causing edits to
legacy/custom agents to be overwritten and to go stale; in AgentForm.jsx update
the UI to accept free-form model strings by replacing the strict <select> bound
to form.model with an <input> plus a <datalist> (or a combobox) populated from
LLM_MODELS, and ensure when loading an existing agent if form.model is not in
LLM_MODELS you programmatically prepend/insert that value into the datalist
options so the displayed value matches the agent and saving preserves it; update
any code that reads LLM_MODELS or renders the select (look for LLM_MODELS,
form.model, and the component rendering the model <select>) to implement this
behavior.
---
Nitpick comments:
In `@frontend/src/App.jsx`:
- Around line 60-65: The effect in App.jsx depends on fetchHistory and
fetchActivity but only lists selectedId, causing an exhaustive-deps lint
warning; update the useEffect dependency array to include fetchHistory and
fetchActivity (i.e., useEffect(..., [selectedId, fetchHistory, fetchActivity]))
or alternatively inline the fetch calls instead of using the callbacks so the
linter is satisfied; ensure you keep the existing conditional on selectedId and
that both callbacks remain stable.
- Around line 72-78: Replace the blocking window.alert calls with a non-blocking
UI feedback mechanism: update reportError to call a toast/banner helper instead
of window.alert and remove any other window.alert usages in handleSendMessage,
handleClearHistory, handleSaveAgent, and handleDeleteAgent; if a Toast component
already exists, call its show method (or set a top-level error state to render
an inline banner), otherwise add a small showToast helper that accepts the
message and severity and invoke that from reportError and the listed handlers
while keeping console.error for diagnostics.
- Around line 25-36: The fetchAgents useCallback should not depend on selectedId
because its sole caller is inside a useEffect with an empty dep array; remove
selectedId from fetchAgents' dependency list and instead read the current value
from the existing ref (e.g., selectedIdRef.current) inside fetchAgents when
deciding whether to setSelectedId; also ensure wherever setSelectedId is called
the ref is kept in sync so selectedIdRef.current reflects updates. This makes
fetchAgents stable (no selectedId in the deps) and avoids the stale-closure /
ESLint react-hooks/exhaustive-deps issue.
In `@frontend/src/components/AgentForm.jsx`:
- Around line 30-47: Add Escape-key and backdrop-click dismissal to the
AgentForm modal: attach an onClick handler to the outer overlay div (the element
with role="dialog" / className="fixed inset-0...") that calls onCancel only when
e.target === e.currentTarget (so clicks inside the inner panel/form do not close
it), and add a useEffect in the AgentForm component that listens for keydown,
calls onCancel when event.key === 'Escape', and cleans up the listener on
unmount; keep existing handleSubmit and inner form behavior unchanged.
- Around line 13-19: The AgentForm component's local state (form, setForm) is
only initialized once via useState and won't update when the agent prop changes;
update AgentForm to resync form when agent changes by adding a useEffect that
watches agent (or agent?.id) and calls setForm({ name: agent?.name || '',
instructions: agent?.instructions || '', model: agent?.model || agent?.llm ||
'gpt-4o-mini', status: agent?.status || 'active' }); alternatively, ensure the
parent keys/unmounts the modal by agent?.id to force remounts — pick one
approach and apply it to AgentForm so editing a different agent updates the form
fields.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5276801d-61d8-4b50-918d-6f760b65bc30
📒 Files selected for processing (2)
frontend/src/App.jsxfrontend/src/components/AgentForm.jsx
AgentForm.jsx: - Replace <select> with <input list="llm-models"> + <datalist> so custom/legacy model strings are preserved on edit instead of being silently overwritten by the first option. Current model is prepended to the datalist when it isn't in the default list. - Add Escape-key listener (useEffect) and backdrop onClick dismiss (e.target === e.currentTarget guard) for standard modal UX. - Re-sync form state via useEffect on agent?.id change so switching edit targets always shows the correct values. App.jsx: - fetchAgents: read selectedIdRef.current instead of closing over selectedId; remove selectedId from useCallback deps to make it stable and fix the stale-closure lint warning. - useEffect for history/activity: add fetchHistory and fetchActivity to the dep array to satisfy exhaustive-deps. - Key AgentForm on editingAgent?.id so React unmounts/remounts on agent switch as a belt-and-suspenders guard alongside the useEffect.
Summary
This PR introduces AgentOS, a complete full-stack web application for managing and interacting with AI agents powered by PraisonAI. The system includes a modern React frontend with a dark-themed UI and a FastAPI backend with SQLite persistence.
Key Changes
Backend (FastAPI)
main.py: FastAPI application with REST endpoints for agent CRUD operations, chat interactions, and activity logging
GET/POST/PUT/DELETE /agents,GET /agents/{id}POST /agents/{id}/chatwith activity trackingGET/DELETE /agents/{id}/historyGET /agents/{id}/activitydb.py: SQLite database layer with schema and migration support
agents_store.json)llmfield alias for backward compatibilityFrontend (React + Vite)
App.jsx: Main application component with state management for agents, chat history, and activity logs
Components:
Styling & Configuration
Data & Configuration
agents_store.json(Research Agent, Code Agent)pyproject.tomlpackage.jsonNotable Implementation Details
llmandmodelfield names, withllmas a legacy aliashttps://claude.ai/code/session_01Kx8vy9hsQL3633e1piffag
Summary by CodeRabbit
New Features
Documentation
Chores